package com.conlin.learn.io;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.time.Duration;
import java.time.Instant;
import java.util.Objects;

/**
 * 通过流复制文件
 * 在 Java 中使用 FileOutputStream 写入文件时，new byte[1024] 和 fileOutputStream.write(bytes, 0, b) 的行为确实会受到缓冲区大小的影响。当您将缓冲区的大小设置为比文件大得多时，虽然写磁盘的次数减少了，但整体耗时却可能增加。以下是几个可能的原因：
 *
 * 1. 操作系统级别的缓存和同步
 * 操作系统缓存：Java 的 I/O 操作通常会通过操作系统的缓存层进行。即使您在 Java 程序中调用了一次 write 方法，操作系统可能会将数据暂时存储在内存中的缓存中，而不是立即写入磁盘。当缓冲区非常大时，操作系统需要处理更多的数据，这可能导致更长的延迟。
 * 同步操作：如果您使用的是 FileOutputStream，默认情况下它不会自动刷新（即不会立即将数据写入磁盘）。但是，当缓冲区足够大时，操作系统可能会触发更多的同步操作，以确保数据被正确写入磁盘。这些同步操作可能会导致额外的延迟。
 * 2. 文件系统元数据更新
 * 文件系统元数据：当您写入一个大块的数据时，文件系统需要更新文件的元数据（如文件大小、时间戳等）。对于较大的写入操作，文件系统的元数据更新可能会更加复杂，导致额外的开销。
 * 3. 磁盘 I/O 性能
 * 磁盘寻道时间和旋转延迟：对于传统的机械硬盘（HDD），磁盘的寻道时间和旋转延迟是影响写入速度的重要因素。当您一次性写入大量数据时，磁盘可能需要花费更多的时间来定位和写入数据，尤其是如果文件较大且分散在磁盘的不同位置。
 * SSD 性能：对于固态硬盘（SSD），虽然没有机械运动，但写入大量数据时仍然可能会遇到性能瓶颈，尤其是在写入放大（Write Amplification）的情况下。SSD 在写入大块数据时可能会触发垃圾回收（Garbage Collection）等后台操作，这也会增加写入时间。
 * 4. JVM 垃圾回收
 * 垃圾回收：当您分配一个非常大的字节数组时，JVM 可能会触发垃圾回收（GC），尤其是在堆内存不足的情况下。垃圾回收过程会暂停应用程序的执行，导致写入操作的延迟增加。
 * 5. 内存映射文件（Memory-Mapped Files）
 * 内存映射文件：如果您使用的是内存映射文件（FileChannel.map），那么大块的写入操作可能会导致更多的页表更新和内存管理开销。虽然内存映射文件可以提高某些场景下的性能，但在处理非常大的文件时，它也可能引入额外的复杂性和延迟。
 * 6. 缓冲区大小与 I/O 效率
 * 适当的缓冲区大小：虽然增大缓冲区可以减少写磁盘的次数，但这并不总是意味着性能会更好。实际上，缓冲区过大可能会导致更多的内存占用，并且操作系统和文件系统可能无法有效地处理如此大的数据块。通常，1KB 到 8KB 的缓冲区大小是一个较为合理的范围，具体取决于您的应用场景和硬件配置。
 * 7. 文件预分配
 * 文件预分配：当您写入一个非常大的文件时，文件系统可能需要动态扩展文件的大小。这种动态扩展可能会导致额外的开销，尤其是在文件系统不支持预分配的情况下。一些文件系统（如 ext4 或 NTFS）支持预分配文件空间，这可以减少文件扩展的开销。
 */
public class CopyFileTest {
    public static void main(String[] args) {

        String path = Objects.requireNonNull(CopyFileTest.class.getClassLoader().getResource("files/cat.png")).getPath();
        String copyPath = Objects.requireNonNull(CopyFileTest.class.getClassLoader().getResource("files/")).getPath();
        try(FileInputStream fileInputStream = new FileInputStream(path);
            FileOutputStream fileOutputStream = new FileOutputStream(copyPath + "cat_copy.png")) {



            Instant start = Instant.now();
//            int b;
//            while ((b = fileInputStream.read()) != -1) {
//                // 一个字节一个字节的写 如果有1000个字节 则会写磁盘1000次 影响性能
//                fileOutputStream.write(b);
//            }
//            fileOutputStream.flush();
            // 不使用缓存  3059 ms
            // 102400 4 ms
            // 1024 8ms 9ms
            // 1024000000 超过文件大小，执行时间跟设置1024一样 甚至更长 545 ms 原因是 多出来的字节数组也会填充内容
            byte[] bytes = new byte[1024];
            int b;
            int num = 0;
            while ((b = fileInputStream.read(bytes)) != -1) {
                // 先将数据读到bytes  然后一次写入bytes  减少对磁盘的io操作
                fileOutputStream.write(bytes,0,b);
                num++;
            }
            System.out.println("写磁盘次数" + num);
            fileOutputStream.flush();

            Instant end = Instant.now();
            Duration duration = Duration.between(start, end);
            System.out.println("Execution time: " + duration.toMillis() + " ms");

        }catch (IOException e) {
            e.printStackTrace();
        }

    }
}
